package com.lagou.edu.factory;

import com.alibaba.druid.util.StringUtils;
import com.lagou.edu.annotation.*;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;

import java.io.InputStream;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.*;

import org.reflections.Reflections;

/**
 * @author 应癫
 * <p>
 * 工厂类，生产对象（使用反射技术）
 */
public class BeanFactory {

    /**
     * 任务一：读取解析xml，通过反射技术实例化对象并且存储待用（map集合）
     * 任务二：对外提供获取实例对象的接口（根据id获取）
     */
    private static Map<String, Object> map = new HashMap<>();  // 存储对象

    static {
        // 任务一：读取解析xml，通过反射技术实例化对象并且存储待用（map集合）
        // 加载xml
        InputStream resourceAsStream = BeanFactory.class.getClassLoader().getResourceAsStream("beans.xml");
        // 解析xml
        SAXReader saxReader = new SAXReader();
        try {
            Document document = saxReader.read(resourceAsStream);
            Element rootElement = document.getRootElement();
            doScanComponent(rootElement);
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (DocumentException e) {
            e.printStackTrace();
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }
    }

    /**
     * 扫描组件
     *
     * @param rootElement 根节点
     * @throws ClassNotFoundException
     */
    private static void doScanComponent(Element rootElement) throws ClassNotFoundException, InvocationTargetException, NoSuchMethodException, InstantiationException, IllegalAccessException {
        List<Element> packageList = rootElement.selectNodes("//context");
        if (packageList != null && packageList.size() > 0) {
            Element element = packageList.get(0);
            String packagename = element.attributeValue("basepackage");
            // 扫描组件
            doLoadBeanDefinition(packagename);
            // 实例化完成之后维护对象的依赖关系
            doScanAutowired();
            // 扫描声明式事务注解
            doScanTransactional();
        }
    }

    // 任务二：对外提供获取实例对象的接口（根据id获取）
    public static Object getBean(String id) {
        return map.get(id);
    }

    private static void doLoadBeanDefinition(String packageName) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
        //扫描获取反射对象集合
        Reflections reflections = new Reflections(packageName);
        Set<Class<?>> services = reflections.getTypesAnnotatedWith(Service.class);
        Set<Class<?>> repositories = reflections.getTypesAnnotatedWith(Repository.class);
        Set<Class<?>> components = reflections.getTypesAnnotatedWith(Component.class);


        for (Class<?> clazz : services) {
            Object bean = clazz.getDeclaredConstructor().newInstance();
            Service annotation = clazz.getAnnotation(Service.class);
            if (StringUtils.isEmpty(annotation.value())) {
                String beanName = lowerCase(clazz.getSimpleName());
                map.put(beanName, bean);
            } else {
                map.put(annotation.value(), bean);
            }
        }

        for (Class<?> clazz : repositories) {
            Object bean = clazz.getDeclaredConstructor().newInstance();
            Repository annotation = clazz.getAnnotation(Repository.class);
            if (StringUtils.isEmpty(annotation.value())) {
                String beanName = lowerCase(clazz.getSimpleName());
                map.put(beanName, bean);
            } else {
                map.put(annotation.value(), bean);
            }
        }

        for (Class<?> clazz : components) {
            Object bean = clazz.getDeclaredConstructor().newInstance();
            Component annotation = clazz.getAnnotation(Component.class);
            if (StringUtils.isEmpty(annotation.value())) {
                String beanName = lowerCase(clazz.getSimpleName());
                map.put(beanName, bean);
            } else {
                map.put(annotation.value(), bean);
            }
        }
    }

    private static void doScanAutowired() throws IllegalAccessException {

        for (Map.Entry<String, Object> entry : map.entrySet()) {

            Object bean = entry.getValue();
            Field[] fields = bean.getClass().getDeclaredFields();

            // 遍历属性，检查是否包含Autowired注解
            for (Field field : fields) {
                if (field.isAnnotationPresent(Autowired.class) && field.getAnnotation(Autowired.class).required()) {
                    String fieldName = field.getType().getSimpleName();
                    String beanName = lowerCase(fieldName);
                    field.setAccessible(true);
                    field.set(bean, map.get(beanName));
                }
            }

        }

    }


    private static void doScanTransactional() {

        for (Map.Entry<String, Object> entry : map.entrySet()) {

            Class clazz = entry.getValue().getClass();
            if (clazz.isAnnotationPresent(Transactional.class)) {
                //获取代理工厂
                ProxyFactory proxyFactory = (ProxyFactory) map.get("proxyFactory");
                if (proxyFactory == null) {
                    System.out.println("没有代理工厂");
                    continue;

                }
                Class<?>[] interfaces = clazz.getInterfaces();
                Object originalObject = getBean(entry.getKey());

                Object proxyObj = doProxyObject(interfaces, proxyFactory, originalObject);
                map.put(entry.getKey(), proxyObj);
            }

        }
    }


    private static Object doProxyObject(Class<?>[] interfaces, ProxyFactory proxyFactory, Object originalObject) {
        Object proxyObj;
        if (interfaces.length == 0) {
            System.out.println("使用cglib");
            proxyObj = proxyFactory.getCglibProxy(originalObject);
        } else {
            System.out.println("使用jdk动态代理");
            proxyObj = proxyFactory.getJdkProxy(originalObject);
        }
        return proxyObj;
    }

    private static String lowerCase(String name) {
        return name.substring(0, 1).toLowerCase() + name.substring(1);
    }


}